import { map, remove, cloneDeep, forEach, findIndex, isNumber, some, filter } from "lodash";
import G6 from "@antv/g6";
/**
 * 处理接口返回节点和调用线的数据（提取公共方法，回溯通用）
 * @param apiNodes 接口返回nodes数据
 * @param apiEdges 接口返回edges数据
 * @param diff 请求时间范围
 */
export const buildApiData = (apiNodes, apiEdges, diff, attachDataType) => {
  // const { attachDataType } = this.state;
  const nodes = map(apiNodes, (node) => {
    return {
      ...node,
      id: node.workloadKey,
      x: node.xaxis,
      y: node.yaxis,
      kind: "node",
      attachDataType,
      timeRange: diff, // 超过该时间范围不展示同环比数据-只需传入timeRange，在custom-node中会做判断
      isNoData: node.inDegrees === 0 && !node.requestTotal,
    };
  });
  const edges = map(apiEdges, (edge) => {
    return {
      ...edge,
      source: edge.srcWorkloadKey,
      target: edge.dstWorkloadKey,
      kind: "edge",
      attachDataType,
    };
  });
  return { nodes, edges };
};

// 获取按照命名空间归并的拓扑数据
export const getNamespaceMergeData = (topoData) => {
  const { nodes, edges } = cloneDeep(topoData);
  const combosEdges = [],
    combos = [];
  // 获取combo之间的调用关系
  forEach(edges, (edge) => {
    // source和target由四元组组成，分别为masterIp，namespace，workloadType和workloadName，以#间隔
    const srcField = edge.source.split("#");
    const dstField = edge.target.split("#");
    if (
      findIndex(combosEdges, {
        source: `${srcField[0]}#${srcField[1]}`,
        target: `${dstField[0]}#${dstField[1]}`,
      }) === -1 &&
      findIndex(combosEdges, {
        target: `${srcField[0]}#${srcField[1]}`,
        source: `${dstField[0]}#${dstField[1]}`,
      }) === -1
    ) {
      combosEdges.push({
        source: `${srcField[0]}#${srcField[1]}`,
        target: `${dstField[0]}#${dstField[1]}`,
      });
    }
  });
  remove(combosEdges, (combosEdge) => combosEdge.source === combosEdge.target);
  // * 构建combo是否有连接的Map
  const hasConnectCombosMap = combosEdges.reduce(
    (accu, { source, target }, index) => {
      accu[source] = accu[target] = true;
      return accu;
    },
    {}
  );
  forEach(nodes, (node) => {
    const comboId = `${node.masterIp}#${node.namespace}`;
    delete node.x;
    delete node.y;
    node.comboId = comboId;

    if (findIndex(combos, { id: comboId }) === -1) {
      combos.push({
        id: comboId,
        label: node.namespace,
        labelText: node.namespace,
        masterIp: node.masterIp,
        masterName: node.masterName,
        namespace: node.namespace,
        collapsed: true,
        hasConnect: hasConnectCombosMap[comboId],
      });
    }
  });

  const resultCombos = getCombosLevel(combos, combosEdges);
  return { nodes, edges, combos: resultCombos };
};

// 根据combos和combo之间的调用关系计算该combo属于哪个层级
const getCombosLevel = (combos, combosEdges, resultCombos = [], level = -1) => {
  let nowLevelCombos = [];
  if (level === -1) {
    // 没有任何调用关系的combo
    nowLevelCombos = remove(combos, (combo) => {
      return (
        findIndex(
          combosEdges,
          (combosEdge) =>
            combosEdge.source === combo.id || combosEdge.target === combo.id
        ) === -1
      );
    });
  } else if (level === 0) {
    // 入度为0的combo
    nowLevelCombos = remove(combos, (combo) => {
      return findIndex(combosEdges, { target: combo.id }) === -1;
    });
  } else {
    // 从combosEdges中过滤出以上一层combo为source的线，去找下一层combo
    const filterEdges = filter(
      combosEdges,
      (combosEdge) => findIndex(resultCombos, { id: combosEdge.source }) > -1
    );
    nowLevelCombos = remove(combos, (combo) => {
      return findIndex(filterEdges, { target: combo.id }) > -1;
    });
  }
  forEach(nowLevelCombos, (combo) => {
    combo.level = level;
  });
  resultCombos = resultCombos.concat(nowLevelCombos);
  if (combos.length > 0) {
    if (nowLevelCombos.length === 0 && level > 0) {
      forEach(combos, (combo) => {
        combo.level = level;
      });
      resultCombos = resultCombos.concat(combos);
    } else {
      resultCombos = getCombosLevel(combos, combosEdges, resultCombos, ++level);
    }
  }
  return resultCombos;
};

// 获取按归并分组的拓扑数据
export const getIpPortMergeData = (topoData) => {
  const { nodes, edges } = cloneDeep(topoData);
  const combos = [];
  forEach(nodes, (node) => {
    delete node.x;
    delete node.y;
    if (node.mergeLabel) {
      node.comboId = node.mergeLabel;
      if (findIndex(combos, { id: node.mergeLabel }) === -1) {
        combos.push({
          id: node.mergeLabel,
          label: node.mergeLabel,
          labelText: node.mergeLabel,
          collapsed: true,
        });
      }
    } else {
      delete node.comboId;
    }
  });
  return { nodes, edges, combos };
};
// 获取不归并的拓扑数据
export const getNoMergeTopoData = (topoData) => {
  const { nodes, edges } = cloneDeep(topoData);
  forEach(nodes, (node) => {
    node.x = node.xaxis;
    node.y = node.yaxis;
    delete node.comboId;
  });
  return { nodes, edges };
};

/**
 * 获取节点和线的状态是否有异常
 * 同环比判定中-只判断当前查看的是同比还是环比数据。attachDataType 0-环比 1-同比
 * 环比数据对比增长超过50%（请求量，错误率，响应时间）
 * 同比数据对比增长超过50%（请求量，错误率，响应时间）
 * 当前错误率超过50%
 * 当前响应时间超过5s
 * @param data 节点或线上数据
 * @returns 1-异常 0-正常
 */
export const getNodeAndEdgeStatus = (data) => {
  const {
    responseTimeDodRate,
    responseTimeRingRate,
    requestTotalDodRate,
    requestTotalRingRate,
    errorRateDodRate,
    errorRateRingRate,
    errorRate,
    responseTime,
    attachDataType,
    errorMessage,
  } = data;
  const dodValues = [
    responseTimeDodRate,
    requestTotalDodRate,
    errorRateDodRate,
  ];
  const ringValues = [
    responseTimeRingRate,
    requestTotalRingRate,
    errorRateRingRate,
  ];

  if (errorMessage?.length) {
    return 1;
  }

  const errorRateExceeds = isNumber(errorRate) && errorRate > 50;
  const responseTimeExceeds =
    isNumber(responseTime) && responseTime > 5000000000;
  if (attachDataType) {
    // 同比
    const dodExceeds = some(
      dodValues,
      (value) => isNumber(value) && Math.abs(value) > 50
    );
    return dodExceeds || responseTimeExceeds || errorRateExceeds ? 1 : 0;
  } else {
    // 环比
    const dodExceeds = some(
      ringValues,
      (value) => isNumber(value) && Math.abs(value) > 50
    );
    return dodExceeds || responseTimeExceeds || errorRateExceeds ? 1 : 0;
  }
};

/**
 * 拓扑combo框展开收缩后操作-将右边和下边元素挤开
 * @param evt
 * @param graph
 * @param config 一些node、combo宽高间距等配置信息
 * @returns
 */
export const comboChange = (evt, graph, config) => {
  const { action, item } = evt;
  if (item.destroyed) return;
  if (!graph) return;
  const { nodeWidth, nodeHeight, wSpace, hSpace, comboSize, comboPadding } = config;
  const nodes = item.getNodes();
  const number = nodes.length;
  const max = Math.ceil(Math.sqrt(number));
  const width = max * (nodeWidth + wSpace) - wSpace + comboPadding[1] + comboPadding[3];
  const height = Math.ceil(number / max) * (nodeHeight + hSpace) - hSpace + comboPadding[0] + comboPadding[2];
  const offsetX = width - (comboSize[0] + comboPadding[1] + comboPadding[3]);
  const offsetY = height - (comboSize[1] + comboPadding[0] + comboPadding[2]);
  const { minX, maxX, minY, maxY } = item.getBBox();
  const nodeOffsetX = 1;
  const nodeOffsetY = 1;
  if (action === "expand") {
      //更新node位置
      nodes.forEach((node, i) => {
          //初始化时不执行
          if (item._xxx) {
              const nodeX = minX + comboPadding[3] + (i % max) * (nodeWidth + wSpace) + nodeOffsetX;
              const nodeY = minY + comboPadding[0] + Math.floor(i / max) * (nodeHeight + hSpace) + nodeOffsetY;
              node.updatePosition({ x: nodeX, y: nodeY });
          }
      });
      nodes.forEach((node) => {
          node.getEdges().forEach((edge) => {
              graph.updateItem(edge, {});
          });
      });

      requestAnimationFrame(() => {
          if (item.destroyed) return;
          // 改变剩余的combo坐标
          if (item._xxx) {
              //获取展开combo的各个边界点
              const combos = graph.getCombos().sort((a, b) => a.getBBox().minY - b.getBBox().minY);
              let mustDown = false;
              combos.forEach((combo) => {
                  if (combo !== item) {
                      const comboBBox = combo.getBBox();
                      const { centerX: x, centerY: y, minX: comboMinX, maxX: comboMaxX, minY: comboMinY, maxY: comboMaxY } = comboBBox;
                      if (!combo.offsetMap) {
                          combo.offsetMap = new Map();
                      }
                      combo.offsetMap[item.getModel().id] = { offsetX: offsetX, offsetY: offsetY };
                      let pointX = x,
                          pointY = y;
                      const allowOffset = 20; // 允许存在的偏差范围
                      const nodes = combo.getNodes();
                      if (
                          // Math.ceil(number / max) > 1 &&
                          // 这两个判断为只往下移动该combo下的节点 暂时注释，有一定的问题
                          // comboMaxX >= minX - allowOffset &&
                          // comboMinX <= maxX + offsetX + allowOffset &&
                          comboMinY > minY + allowOffset
                      ) {
                          if (comboMinY <= maxY + offsetY + allowOffset || mustDown) {
                              mustDown = true;
                              // 寻找offsetMap中与当前展开项同层的已展开comboId，在向下偏移量中减去此comboId下的偏移量
                              let key_D;
                              forEach(combo.offsetMap, (value, key) => {
                                  if (
                                      value.direction &&
                                      value.direction === 'D' &&
                                      value.minY <= minY + allowOffset &&
                                      value.minY >= minY - allowOffset
                                  ) {
                                      key_D = key;
                                  }
                              })
                              key_D && (combo.offsetMap[key_D].direction = '_D');
                              const offsetY_D = key_D ? combo.offsetMap[key_D].offsetY : 0;
                              // 向下移动
                              combo.offsetMap[item.getModel().id].direction = "D";
                              combo.offsetMap[item.getModel().id].minY = minY;
                              combo.offsetMap[item.getModel().id].maxY = maxY + offsetY;
                              pointY = y + offsetY - offsetY_D;
                              nodes.forEach((node) => {
                                  const { y: nodeY } = node.getBBox();
                                  node.updatePosition({ y: nodeY + offsetY + nodeOffsetY - offsetY_D });
                              });
                          } else {
                              // 不进行向下移动，direction标记为'_D'
                              combo.offsetMap[item.getModel().id].direction = "_D";
                              combo.offsetMap[item.getModel().id].maxY = maxY + offsetY;
                          }
                      } else if (
                          // max > 1 &&
                          comboMinY <= minY + allowOffset &&
                          comboMinY >= minY - allowOffset &&
                          // 这两个判断为只向右横移该combo右方的节点 暂时注释，有一定的问题
                          // comboMaxY >= minY - allowOffset &&
                          // comboMinY <= maxY + offsetY + allowOffset &&
                          comboMinX > minX + allowOffset
                      ) {
                          // 向右横移
                          pointX = x + offsetX;
                          combo.offsetMap[item.getModel().id].direction = "R";
                          nodes.forEach((node) => {
                              const { x: nodeX } = node.getBBox();
                              node.updatePosition({ x: nodeX + offsetX + nodeOffsetX });
                          });
                      }
                      nodes.forEach((node) => {
                          node.getEdges().forEach((edge) => {
                              graph.updateItem(edge, {});
                          });
                      });
                      combo.updatePosition({ x: pointX, y: pointY + 19 });
                  }
              });
              delete item._xxx;
          }
      });
  } else {
      requestAnimationFrame(() => {
          if (item.destroyed) return;
          //改变剩余的combo坐标
          if (item._xxx) {
              item.updatePosition({ x: minX + 82.5, y: minY + 54.5 });
              const combos = graph.getCombos();
              combos.forEach((combo) => {
                  if (combo.destroyed) return;
                  if (combo !== item && combo.offsetMap) {
                      const nodes = combo.getNodes();
                      const sourceBBox = combo.getBBox();
                      const { centerX: x, centerY: y, width: comboWidth } = sourceBBox;
                      const { offsetX, offsetY, direction } = combo.offsetMap[item.getModel().id];
                      let pointX = x,
                          pointY = y;
                      switch (direction) {
                          case "R":
                              pointX = x - offsetX;
                              nodes.forEach((node) => {
                                  const { x: nodeX } = node.getBBox();
                                  node.updatePosition({ x: nodeX - offsetX + nodeOffsetX });
                              });
                              break;
                          case "D":
                              // 寻找offsetMap中direction为'_D'、maxY>当前收缩combo的minY且maxY最大的key，在向上收缩后需加回该key的offsetY
                              let key_D, maxY_D;
                              forEach(combo.offsetMap, (value, key) => {
                                  if (value.direction && value.direction === '_D' && value.maxY > minY) {
                                      if (isNumber(maxY_D)) {
                                          if (value.maxY > maxY_D) {
                                              maxY_D = value.maxY;
                                              key_D = key;
                                          }
                                      } else {
                                          maxY_D = value.maxY;
                                          key_D = key;
                                      }
                                  }
                              })
                              key_D && (combo.offsetMap[key_D].direction = 'D');
                              const offsetY_D = key_D ? combo.offsetMap[key_D].offsetY : 0;
                              pointY = y - offsetY + offsetY_D;
                              nodes.forEach((node) => {
                                  const { y: nodeY } = node.getBBox();
                                  node.updatePosition({ y: nodeY - offsetY + nodeOffsetY + offsetY_D });
                              });
                              break;
                          default:
                              break;
                      }

                      combo.updatePosition({ x: pointX, y: pointY + 19 });
                      delete combo.offsetMap[item.getModel().id];
                      nodes.forEach((node) => {
                          node.getEdges().forEach((edge) => {
                              edge.toFront();
                              graph.updateItem(edge, {});
                          });
                      });
                  }
              });
              delete item._xxx;
          }
      });
  }
  setTimeout(() => {
      graph.refreshPositions();
  })
};

// draw之后再add external节点和相关调用线
export const addExternal = (apiNodes, graph) => {
  const { externalNodes, externalEdges } = getExternalNodesEdges(apiNodes);
  if (externalNodes.length) {
      graph.addItem('node', externalNodes[0]);
      forEach(externalEdges, edge => {
          graph.addItem('edge', edge);
      })
  }
}

/**
 * 获取文字在dom中的宽度
 * @param text 文本内容
 * @param fontSize 字体大小
 * @returns 宽度
 */
export const getTextWidth = (text, fontSize) => {
  const _span = document.createElement('span');
  _span.innerText = text;
  _span.style.fontSize = fontSize + 'px';
  _span.style.position = 'absolute';
  document.body.appendChild(_span);
  let width = _span.offsetWidth;
  document.body.removeChild(_span);
  return width;
}

/**
 * format the string in topo
 * @param {string} str The origin string
 * @param {number} maxWidth max width
 * @param {number} fontSize font size
 * @return {string} the processed result
 */
export const fittingString = (str, maxWidth, fontSize) => {
  const ellipsis = "...";
  const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
  let currentWidth = 0;
  let res = str;
  const pattern = new RegExp("[\u4E00-\u9FA5]+"); // distinguish the Chinese charactors and letters
  str?.split("").forEach((letter, i) => {
      if (currentWidth > maxWidth - ellipsisLength) return;
      if (pattern.test(letter)) {
          // Chinese charactors
          currentWidth += fontSize;
      } else {
          // get the width of single letter according to the fontSize
          currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth - ellipsisLength) {
          res = `${str.substr(0, i)}${ellipsis}`;
      }
  });
  return res;
};
export const wordLength = (str, maxWidth, fontSize) => {
  const ellipsis = "...";
  const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
  let currentWidth = 0;
  let res = str;
  const pattern = new RegExp("[\u4E00-\u9FA5]+"); // distinguish the Chinese charactors and letters
  str?.split("").forEach((letter, i) => {
      if (currentWidth > maxWidth - ellipsisLength) return;
      if (pattern.test(letter)) {
          // Chinese charactors
          currentWidth += fontSize;
      } else {
          // get the width of single letter according to the fontSize
          currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth - ellipsisLength) {
          res = `${str.substr(0, i)}${ellipsis}`;
      }
  });
  const width = currentWidth > maxWidth - ellipsisLength ? maxWidth: currentWidth
  return Number(width.toFixed(2));
}
export const formatDodRingData = (percent) => {
  if (isNumber(percent)) {
      const t_percent = Math.abs(percent);
      if (t_percent > 0 && t_percent < 0.1) {
          return '<0.1%';
      }
      return `${+t_percent.toFixed(1)}%`;
  }
  return '--';
}

// 拓扑图搜索节点focus
export const graphFocusNode = (item, graph, isFocus = false) => {
  const allNodes = graph.getNodes();
  const allEdges = graph.getEdges();
  const allCombos = graph.getCombos();
  // ! 折叠起来的combo只能通过combo来查edge
  const allEdgesPlus = allCombos.reduce((prev, cur) => {
      prev.push(...cur.getEdges())
      return prev
  }, []);

  if (item) {
      const nodeId = item.getModel().id;

      [].concat(allEdgesPlus, allNodes, allEdges).forEach((item) => {
          const group = item.getContainer()
          const children = group.get('children')
          children.forEach((item) => {
              item.attr({
                  opacity: 0.2
              })
          })

      })
      // * 获取关联的边
      const relatedEdges = item.getEdges()
      //* 根据边找到所有的关联节点和边
      // ! 通过边来查找关系,会导致一个问题,没有边的节点,查不到任何节点和边
      const sourceNodes = relatedEdges.map((item) => item.getSource().getModel().id)
      const targetNodes = relatedEdges.map((item) => item.getTarget().getModel().id)
      const edges = relatedEdges.map((item) => item.getModel().id)
      // * 去重之后高亮
      Array.from(new Set([].concat(sourceNodes, targetNodes, edges, nodeId))).forEach((id) => {
          const graphItem = graph.findById(id)
          const itemChildren = graphItem.get('group').get('children')
          itemChildren.forEach((item) => {
              item.attr({ opacity: 1 })
          })
      })

      isFocus && graph.focusItem(item);
  } else {
      [].concat(allNodes, allEdges, allEdgesPlus).forEach((item) => {
          const group = item.getContainer()
          const children = group.get('children')
          children.forEach((item) => {
              item.attr({
                  opacity: 1
              });
          })
      });
  }
}
// 拓扑图搜索节点focus
export const graphFocusNodes = (items, graph, isFocus = false) => {
  const allNodes = graph.getNodes();
  const allEdges = graph.getEdges();
  const allCombos = graph.getCombos();
  // ! 折叠起来的combo只能通过combo来查edge
  const allEdgesPlus = allCombos.reduce((prev, cur) => {
      prev.push(...cur.getEdges())
      return prev
  }, []);

  if (items.length) {
      const nodeIds = items.map((item) => item.getModel().id);

      [].concat(allEdgesPlus, allNodes, allEdges).forEach((item) => {
          const group = item.getContainer()
          const children = group.get('children')
          children.forEach((item) => {
              item.attr({
                  opacity: 0.2
              })
          })

      })

      // * 去重之后高亮
      Array.from(new Set(nodeIds)).forEach((id) => {
          const graphItem = graph.findById(id)
          const itemChildren = graphItem.get('group').get('children')
          itemChildren.forEach((item) => {
              item.attr({ opacity: 1 })
          })
      })

      isFocus && graph.focusItem(item);
  } else {
      [].concat(allNodes, allEdges, allEdgesPlus).forEach((item) => {
          const group = item.getContainer()
          const children = group.get('children')
          children.forEach((item) => {
              item.attr({
                  opacity: 1
              });
          })
      });
  }
}
// 清除拓扑图所有状态样式
export const clearAllStats = (graph) => {
  if (!graph) {
      return;
  }
  graph.setAutoPaint(false);
  graphFocusNode('', graph);
  graph.getNodes().forEach((node) => {
      graph.clearItemStates(node);
  });
  graph.paint();
  graph.setAutoPaint(true);
}

// 全屏样式
export const fullScreenStyle = {
  position: 'fixed',
  width: '100vw',
  height: '100vh',
  padding: 12,
  left: 0,
  top: 0,
  zIndex: 200,
  background: 'white'
};

export const filterByFieldAndCount = ({nodes, edges, combos},field, count) => {
  let targetNodes = cloneDeep(nodes)
  targetNodes.sort((x, y) =>  y[field] || 0 - x[field] || 0)
  targetNodes  = targetNodes.slice(0, count)
  const maxValue = Math.max(...targetNodes.map((item) => item[field]))
  targetNodes.forEach((item) => {
    item.sortField = field;
    item.maxSortFieldValue = maxValue
  })
  const nodesId = targetNodes.reduce((accu, item) => {
    accu[item.id] = true
    return accu
  }, {})
  // * 根据节点生成边
  const targetEdges = edges.filter((edge) => {
    const {source, target} = edge
    return source in nodesId && target in nodesId
  })
  console.log(targetNodes, 'nodes', targetEdges);

  return {
    nodes: targetNodes,
    edges: targetEdges,
    combos
  }
}
